package tr.edu.ege.communicator;

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;
import java.util.Random;
import java.util.Vector;

import tr.edu.ege.communicator.util.CommunicatorLogger;
import tr.edu.ege.communicator.util.IPFinder;
import tr.edu.ege.communicator.util.MessageQueue;

/**
 * 
 * The main class that represents a node of Peer-to-Peer architecture.
 * 
 * @author Onur ULUSU
 * 
 */
public class Peer {

	/**
	 * Listeners of this peer's message queue.
	 */
	private List<MessageListener> listeners = new Vector<MessageListener>();

	/**
	 * Queue instance that holds the messages..
	 */
	private MessageQueue queue = new MessageQueue();

	/**
	 * The default port number.
	 */
	private int portNumber = 5000;

	/**
	 * Client socket which messages will be sent over.
	 */
	private Socket clientSocket;

	private boolean listening = true;

	/**
	 * Default constructor.
	 * 
	 */
	public Peer() {
		super();
		// Random port number to avoid port conflict.
		this.setPortNumber(new Random().nextInt(5000));
		// starting to listen connection requests..
		new ServerThread(this).start();
	}

	/**
	 * Sends the given message to recipient.
	 * 
	 * @param message
	 *            to send
	 */
	public boolean send(Message message) {
		clientSocket = null;

		// getting IP address and port number of the destination from message..
		Address receiverAddress = message.getReciever();
		InetAddress address = receiverAddress.getAddress();
		int portNumber = receiverAddress.getPortNumber();
		// setting this peer's IP address and port number to the message.
		message.setSender(new Address(
				IPFinder.findNetworkIP().getHostAddress(), this.portNumber));

		try {
			// creating a new socket with given address and port number..
			clientSocket = new Socket(address, portNumber);
		} catch (Exception e) {
			// handle exception.
			CommunicatorLogger.log("An error occured while connecting to %s ",
					address + ":" + portNumber);
			return false;
			// e.printStackTrace();
		}
		// creating an output stream to serialize the message..
		ObjectOutputStream oos;
		try {
			oos = new ObjectOutputStream(clientSocket.getOutputStream());
			oos.writeObject(message);
		} catch (IOException e) {
			CommunicatorLogger
					.log("An error occured while creating output stream..");
			e.printStackTrace();
			return false;
		}
		CommunicatorLogger.log("Message is sent successfully");
		return true;
	}

	/**
	 * A thread that listens connection requests
	 * 
	 * @author Onur ULUSU
	 * 
	 */
	private class ServerThread extends Thread {
		private Peer peer;

		/**
		 * Creates an instance of this class.
		 * 
		 * @param peer
		 */
		public ServerThread(Peer peer) {
			this.peer = peer;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Runnable#run()
		 */
		@Override
		public void run() {
			super.run();
			ServerSocket serverSocket = null;
			try {
				// Creating the socket with given port number..
				serverSocket = new ServerSocket(getPortNumber(), 100);
			} catch (IOException e) {
				// socket creation error..
				CommunicatorLogger
						.log(
								"An error occured while opening a socket at port %d. It may be in use.",
								getPortNumber());
				e.printStackTrace();
			}
			// Socket creation is successful..
			CommunicatorLogger.log("Peer is listening at port %d",
					getPortNumber());
			while (listening) {
				try {
					// Creating a new PeerThread instance and accepting the
					// connection request..
					new PeerThread(serverSocket.accept(), peer).start();
					// Closing the socket and terminating the connection..
				} catch (IOException e) {
					// Connection accepting error..
					CommunicatorLogger
							.log("An error occured while accepting the connection request.");
					e.printStackTrace();
				}
			}
			try {
				serverSocket.close();
			} catch (IOException e) {
				e.printStackTrace();
			}

		}
	}

	/**
	 * Add a listener to be notified when a new message is added to the queue.
	 * 
	 * @param listener
	 */
	public void addListener(MessageListener listener) {
		this.listeners.add(listener);
	}

	/**
	 * Notifies each listener when a new message is added to queuee.
	 */
	public void notifyAllListeners() {
		for (MessageListener listener : this.listeners)
			listener.messageReceived();
	}

	/**
	 * @param portNumber
	 *            the portNumber to set
	 */
	public void setPortNumber(int portNumber) {
		this.portNumber = portNumber;
	}

	/**
	 * @return the portNumber
	 */
	public int getPortNumber() {
		return portNumber;
	}

	/**
	 * @return the queue
	 */
	public MessageQueue getQueue() {
		return queue;
	}
}
